<?php

namespace Wonfu\Core\Route;

use Doctrine\ORM\Mapping\Annotation;
use Symfony\Component\Yaml\Dumper;
use Wonfu\Core\Annotations\RequestMapping;
use Wonfu\Core\Annotations\RequestTag;
use Wonfu\Core\Annotations\Valid;
use Wonfu\Openapi\Component;
use Wonfu\Openapi\Info;
use Wonfu\Openapi\MediaType;
use Wonfu\Openapi\Operation;
use Wonfu\Openapi\Path;
use Wonfu\Openapi\RequestBody;
use Wonfu\Openapi\Response;
use Wonfu\Openapi\Openapi;
use Wonfu\Openapi\Schema;
use Wonfu\Openapi\Server;
use Wonfu\Openapi\Tag;
use Wonfu\Reflections\MethodWrapper;

class OpenapiGenerator
{

    /**
     * @var MethodWrapper[] $methods
     */
    private $methods;


    /**
     * @var Info
     */
    private $info;

    /**
     * @var Server[]
     */
    private $servers;

    /**
     * @var Tag[]
     */
    private $tags;


    /**
     * @var Openapi
     */
    private $openapi;


    /**
     * @param MethodWrapper[] $methods
     */
    public function __construct(array $methods)
    {
        $this->methods = $methods;
        $root = new Openapi();
        $root->openapi = '3.0.3';
        $this->openapi = $root;
        $this->openapi->components = new Component();
    }

    /**
     * @return Info
     */
    public function getInfo(): Info
    {
        return $this->info;
    }

    /**
     * @param Info $info
     */
    public function setInfo(Info $info): void
    {
        $this->info = $info;
    }

    /**
     * @return Server[]
     */
    public function getServers(): array
    {
        return $this->servers;
    }

    /**
     * @param Server[] $servers
     */
    public function setServers(array $servers): void
    {
        $this->servers = $servers;
    }


    /**
     * @return Openapi
     */
    public function organization(): Openapi
    {

        $this->openapi->info = $this->getInfo();
        $this->openapi->servers = $this->getServers();
        $this->tags = [];
        $paths = [];
        foreach ($this->methods as $method) {
            /** @var RequestMapping $requestMapping */
            $requestMapping = $method->getAnnotation();
            /** @var RequestTag $requestTag */
            $requestTag = $method->getClassAnnotation();
            $responses = [];
            $response = $this->structuralReponse($method);
            $responses['200'] = $response;
            $operation = new Operation();
            $operation->summary = $requestMapping->name;
            $operation->operationId = $method->getReflectionMethod()->class . "::" . $method->getReflectionMethod()->name;
            $operation->responses = $responses;
            if ($requestTag) {
                $tag = new Tag();
                $tag->name = $requestTag->name;
                $tag->description = $requestTag->description;
                if (!$this->checkTagExist($tag)) {
                    $this->tags[] = $tag;
                }
                $operation->tags = [$requestTag->name];
            }
            $requestBody = $this->structuralRequestBody($method);
            if ($requestBody) {
                $operation->requestBody = $requestBody;
            }
            $path = new Path();
            $path->{strtolower($requestMapping->method)} = $operation;
            $pathkey = $this->formatPath($requestMapping->path);
            $paths[$pathkey] = $path;
        }
        $this->openapi->paths = $paths;
        $this->openapi->tags = $this->tags;
        return $this->openapi;
    }

    /**
     * @param MethodWrapper $method
     */
    private function structuralParameters($method)
    {
        /** @var Valid $annotation */
        if (!$annotation = $method->getAnnotationWithName(Valid::class)) {
            return false;
        }

    }

    /**
     * @param MethodWrapper $method
     */
    private function structuralRequestBody($method)
    {
        /** @var Valid $annotation */
        if (!$annotation = $method->getAnnotationWithName(Valid::class)) {
            return false;
        }
        if ($annotation->validParamsIn !== Valid::IN_REQUEST_BODY) {
            return false;
        }
        $requestBody = new RequestBody();
        $requestBody->required = true;
        /** @var \ReflectionParameter[] $parameters */
        $parameters = $method->getReflectionMethod()->getParameters();
        $schemas = new Schema();
        $schemas->type = "object";
//        $schemas->required = [];
        $schemas->properties = [];
        foreach ($parameters as $parameter) {
            if ($annotation->paramKey == $parameter->name) {
                $schemaReflection = new \ReflectionClass($parameter->getType()->getName());
                foreach ($schemaReflection->getProperties() as $property) {
                    $schema = new Schema();
                    $schema->type = 'string';
                    $schemas->properties["$property->name"] = $schema;
                }
            }
        }
        $this->openapi->components->schemas[$parameter->getType()->getName()] = $schemas;
        $requestBody->description = '';
        $requestBody->required = true;
        $mediaType = new MediaType();
        $mediaType->schema = ['$ref' => '#/components/schemas/' . $parameter->getType()->getName()];
        $requestBody->content[$annotation->contentType] = $mediaType;
        return $requestBody;
    }

    /**
     * @param MethodWrapper $method
     */
    private function structuralReponse($method)
    {
        $mediaType = new MediaType();
        $schemas = new Schema();
        $returnType = $method->getReflectionMethod()->getReturnType();
        if ($returnType == null || $returnType->isBuiltin()) {
            $response = new Response();
            $schemas->type = 'string';
            $mediaType->schema = $schemas;
            $response->content['text/html'] = $mediaType;
            $response->description = '';
            return $response;
        }
        $schemaReflection = new \ReflectionClass($returnType->getName());
        if (!$returnType->isBuiltin()) {

            $schemas->type = "object";
//            $schemas->required = [];
            $schemas->properties = [];
            foreach ($schemaReflection->getProperties() as $property) {
                $schema = new Schema();
                $schema->type = 'string';
                $schemas->properties["$property->name"] = $schema;
            }
            $mediaType->schema = $schemas;
        }
        $response = new Response();
        $response->description = '';
        $response->content['application/json'] = $mediaType;
        $this->openapi->components->responses[$returnType->getName()] = $response;
        return ['$ref' => '#/components/responses/' . $returnType->getName()];
    }

    private function formatPath(string $path): string
    {
        return preg_replace(['/:\\\d\+/','/\{/','/\}/'], ['','$',''], $path);
    }

    /**
     * @param Tag $tag
     * @return bool
     */
    private function checkTagExist(Tag $tag): bool
    {
        foreach ($this->tags as $v) {
            /** @var Tag $v */
            if ($v->name == $tag->name) {
                return true;
            }
        }
        return false;
    }


}